import glob
import os

PIKAPYTHON_PATH='components/pikapython'
PIKA_BINDING_PATH ='components/pikapython/pikascript-api/__pikaBinding.c'
PIKA_BINDING_FILE = os.path.split(PIKA_BINDING_PATH)[1]

CAPTURE_PATHS=[
    "%(m)s.pyi",
    "%(m)s.py",
    "pikascript-api/%(m)s*.h",
    "pikascript-lib/%(m)s/*.c",
    "pikascript-lib/%(m)s/*.h",
    "pikascript-lib/PikaStdLib/%(m)s*.c",
    ]

CAPTURE_PATHS_BUILTINS_APPEND=[
    "pikascript-core/*.c",
    "pikascript-core/%(m)s*.h",
    ]

# .h and .c defintion of some keys are not match, convert it for comparison
CONVERT_KEYS={
    'pika_bool':'PIKA_BOOL',
    }

class FDICT:
    EXTS={
        '.h':'h',
        '.c':'c',
        '.py':'py',
        '.pyi':'pyi',
        }
    KEYS = ['.pyi','.py','.h','.c']
    def __init__(self):
        EXTS = self.EXTS
        self.fdict={}
        for ext in EXTS:
            self.fdict[EXTS[ext]]=[]

    def __getitem__(self, key):
        return self.fdict.get(key, [])
    
    def add(self, fpath):
        EXTS = self.EXTS        
        for ext in EXTS:
            if fpath.endswith(ext):
                self.fdict[EXTS[ext]].append(fpath)

    def dump(self):
        KEYS = self.KEYS
        EXTS = self.EXTS
        print()
        for key in KEYS:
            extkey = EXTS[key]
            fs = self.fdict[extkey]
            if fs:
                print('File start with %s' % EXTS[key])
                print('-------------------')
                for fn in fs:
                    print(fn)
                print()

        
fdict=FDICT()

import re
r_header_func=r'(\w+)(\*?)\s+(\*?)(\w+)\s*\(.*\);'
re_header_func = re.compile(r_header_func)

r_impl_func =r'(\w+)(\*?)\s+(\*?)(\w+)\s*\(.*\)\s*\{?\s*$'
re_impl_func = re.compile(r_impl_func)

r_impl_func_part=r'(\w+)(\*?)\s+(\*?)(\w+)\s*\(.*\,\s*$'
re_impl_func_part = re.compile(r_impl_func_part)

DROP_KEYS=['IRAM_ATTR', 'typedef']


class CHECKER:
    def __init__(self, mod_name):
        self.mod_name=mod_name
        self.h_funcs=[]  # funcs pre-found in .h
        self.funcs={}    # funcs in .c
        binding_func_keys, linenos= self.get_func_keys(PIKA_BINDING_PATH, re_impl_func, ret_lineno=True)
        i=0
        for fk in binding_func_keys:
            self.funcs[fk]= PIKA_BINDING_FILE, linenos[i]
            i+=1
            
        print('check %s ----------------' % PIKA_BINDING_FILE)
        for item in binding_func_keys:
            print(item)

        print()
        self.binding_func_keys = binding_func_keys
    
    def get_func_keys(self, path, re_func, re_func_part=None, ret_lineno=False, h_funcs=[]):
        func_keys=[]
        linenos =[]
        f =open(path)
        lines = f.readlines()
        f.close()
        lineno=0
        for line in lines:
            lineno+=1
            line = line.strip()
            for dk in DROP_KEYS:
                if dk in line:
                    line = line.replace(dk,'')
                    
            if not self.mod_name in line:
                if len(h_funcs):
                    m = re_func.search(line)
                    if m is None:
                        if re_func_part is not None:
                            m = re_func_part.search(line)
                    if m is not None:
                        func_key = self.make_func_key(m)
                        for h_func in h_funcs:
                            if func_key == h_func:
                                func_keys.append(func_key)
                                linenos.append(lineno)
                                break
                    m = re_func
                continue
            if line.startswith('return'):
                continue
            #print('line:', line)
            m = re_func.search(line)
            if m is None:
                if re_func_part is not None:
                    m = re_func_part.search(line)                            

            if m is not None:
                func_key = self.make_func_key(m)
                func_keys.append(func_key)
                linenos.append(lineno)
                #print('func_key:', func_key)
        if ret_lineno:
            return func_keys, linenos
        return func_keys

    def make_func_key(self, m):
        groups = list(m.groups())
        func_key=groups[0]
        if func_key in CONVERT_KEYS:
            func_key = CONVERT_KEYS[func_key]
        for item in groups[1:]:
            if item=='':
                continue
            func_key +=' ' + item
        return func_key
            
        
    def check_header_item(self, line):
        m = re_header_func.search(line)
        #print(line)
        #print('m:', m.groups())
        if m is not None:
            func_key = self.make_func_key(m)
            #print('func:', func_key)
            if func_key in self.funcs:
                print('func:[%s(...)] -> %s' % (func_key, self.funcs[func_key]))
            else:
                print('func:[%s(...)] -> "???" - cannot match' % (func_key,))
            #if func_key in self.binding_func_keys:
            #    print('func:[%s(...)] -> implemented in %s' % (func_key, PIKA_BINDING_FILE))
                
            
    def check(self):
        print()
        print('pre-check .h files-----------------')
        hfiles = fdict['h']
        for hfn in hfiles:
            print()
            print(hfn)
            print('-----------')
            h_func_keys = self.get_func_keys(hfn, re_header_func)
            for k in h_func_keys:
                if k not in self.h_funcs:
                    self.h_funcs.append(k)
                    print(k)
            print()
                    
        print('check c files')
        cfiles = fdict['c']
        for cfpath in cfiles:
            print()
            print(cfpath)
            print('--------------')
            cfn = os.path.split(cfpath)[1]
            func_keys, linenos = self.get_func_keys(cfpath, re_impl_func, re_impl_func_part, ret_lineno=True, h_funcs=self.h_funcs)
            if len(func_keys) >0:
                print('func_Keys in %s' % cfpath)
                print('-------------------')
                i=0
                for fk in func_keys:
                    self.funcs[fk]= cfn, linenos[i]
                    print(fk)
                    i+=1

        print()
        print('check .h files-----------------')
        hfiles = fdict['h']
        for hfn in hfiles:
            print()
            print(hfn)
            print('-----------')
            f=open(hfn)
            lines = f.readlines()
            f.close()
            for line in lines:
                line =line.strip()
                for dk in DROP_KEYS:
                    if dk in line:
                        line = line.replace(dk,'')
                    
                if self.mod_name in line:
                    if line.startswith('#'):
                        continue
                    self.check_header_item(line)


def list_files(mod_name):
    cdict = {'m': mod_name}
    capture_paths = CAPTURE_PATHS
    if mod_name=='builtins' or mod_name=='PikaObj':
        capture_paths +=  CAPTURE_PATHS_BUILTINS_APPEND
        
    for cpath in capture_paths:
        cpath = cpath % cdict
        globpath =os.path.join(PIKAPYTHON_PATH, cpath)
        #print('globpath:%s' % globpath)
        fs = glob.glob(globpath)
        #print(fs)
        for fn in fs:
            fdict.add(fn)
                
def pyi_brief(pyi_path):
    f=open(pyi_path)
    lines = f.readlines()
    f.close()
    brief=[]
    for line in lines:
        linex = line.strip()
        if linex.startswith('class') or linex.startswith('def'):
            brief.append(line)
    text =''.join(brief)
    print()
    print(pyi_path)
    print('----------------------------')
    print(text)
    print()
    
def main(module):
    if not module.endswith('.pyi'):
        module += '.pyi'
    pyi_path =os.path.join(PIKAPYTHON_PATH, module)
    if not os.path.isfile(pyi_path):
        print('no such pyi module:', module)
        print()
        print_usage()

    mod_name = module[:-4]  # strip '.pyi'
    list_files(mod_name)
    
    if mod_name.startswith('_'):
        # if it startswith _
        # also list files without _
        list_files(mod_name[1:])

    fdict.dump()
    
    checker = CHECKER(mod_name)
    checker.check()
    pyi_brief(pyi_path)
    
def list_pyi_modules():
    fs =glob.glob(os.path.join(PIKAPYTHON_PATH,'*.pyi'))
    for fpath in fs:
        fbase, fn= os.path.split(fpath)
        print(fn)
        
def print_usage():
    help="""Usage:
python chk_pyi.py <pyi module>"""
    print(help)
    print('\nAvailable pyi modules:')
    print('------------------------')
    list_pyi_modules()
    
if __name__=='__main__':
    import sys
    argv = sys.argv
    if len(argv) <2:
        print_usage()
        sys.exit(1)
    elif len(argv)>=2:
        module = argv[1]
        
    main(module)
    
